<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="soulzookeeper-bootstrap">
      Soul网关源码解析（十五）Zookeeper数据同步-Bootstrap端
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      本篇文章对Soul网关Bootstrap的Zookeeper数据同步进行探索
     </p>
     <h2 id="_2">
      示例启动
     </h2>
     <h3 id="_3">
      环境配置
     </h3>
     <p>
      使用docker启动mysql和zk
     </p>
     <p>
      ```shell script
docker run -dit --name zk -p 2181:2181 zookeepe
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="o">###</span> <span class="n">Soul</span><span class="o">-</span><span class="n">Admin配置</span>
<span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="err">启动时候，我们先把</span><span class="n">Soul</span><span class="o">-</span><span class="n">admin和Soul</span><span class="o">-</span><span class="n">Bootstrap的Websocket同步给关了</span><span class="err">，开启</span><span class="n">HTTP同步</span><span class="err">，配置大致如下：</span>

<span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="o">&amp;</span><span class="n">ensp</span><span class="p">;</span><span class="n">Soul</span><span class="o">-</span><span class="n">Admin的HTTP同步方式配置</span><span class="err">，暂时把其他同步方式给注释关闭</span>

<span class="o">```</span><span class="n">xml</span>
  <span class="n">sync</span><span class="p">:</span>
      <span class="n">zookeeper</span><span class="p">:</span>
          <span class="n">url</span><span class="p">:</span> <span class="n">localhost</span><span class="p">:</span><span class="mi">2181</span>
          <span class="n">sessionTimeout</span><span class="p">:</span> <span class="mi">5000</span>
          <span class="n">connectionTimeout</span><span class="p">:</span> <span class="mi">2000</span>
</code></pre>
     </div>
     <h3 id="soul-bootstrap">
      Soul-Bootstrap配置
     </h3>
     <p>
      Soul-Bootstrap的Http同步方式配置，暂时把其他同步方式给注释关闭
     </p>
     <div class="codehilite">
      <pre><span></span><code>soul :
    file:
      enabled: true
    corss:
      enabled: true
    dubbo :
      parameter: multi
    sync:
        zookeeper:
             url: localhost:2181
             sessionTimeout: 5000
             connectionTimeout: 2000
</code></pre>
     </div>
     <h3 id="_4">
      启动失败
     </h3>
     <p>
      启动Soul-admin、Soul-Bootstrap、Soul-Example-HTTP
     </p>
     <p>
      http://localhost:9195/dubbo/findById?id=1 ,访问链接失败
     </p>
     <p>
      进行debug的时候发现没有数据，只用变更的时候会发数据过来，重启了试试不行
     </p>
     <p>
      感觉可能是bug，但是首先排除下自己的代码版本问题和自己的使用问题，问了问群里的老哥，发现是自己的使用问题
     </p>
     <h3 id="_5">
      启动修复
     </h3>
     <p>
      解决的办法是拉一次自己的代码，保证是最新的，然后删除zookeeper里面的soul数据，重启Admin和Bootstrap，重新访问，成功
     </p>
     <p>
      下面是zk图形化客户端，用起来比价方便，win也可以使用（win下第一次启动可能显示不全，关闭再启动即可），下面是搜索到的安装教程和下载链接：
     </p>
     <ul>
      <li>
       <a href="https://blog.csdn.net/kxj19980524/article/details/86840558">
        zookeeper图形化客户端工具ZooInspector的使用
       </a>
      </li>
      <li>
       https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip
      </li>
     </ul>
     <h2 id="_6">
      源码查看
     </h2>
     <p>
      我们直接定位到
      <a href="https://juejin.cn/post/6920596173925384206">
       Soul网关源码阅读（十二）数据同步初探
      </a>
      中的zk同步的入口类： ZookeeperSyncDataService
     </p>
     <p>
      稍微用过zk的，就可以看到熟悉的watch，监听数据的变化，从下面的代码大致可以看出监听我前面分析的五种数据：插件数据、选择器数据、规则数据、AppAuth数据、Metadata数据
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ZookeeperSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="nf">ZookeeperSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ZkClient</span> <span class="n">zkClient</span><span class="p">,</span> <span class="kd">final</span> <span class="n">PluginDataSubscriber</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span>
                                    <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;</span> <span class="n">authDataSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">zkClient</span> <span class="o">=</span> <span class="n">zkClient</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">pluginDataSubscriber</span> <span class="o">=</span> <span class="n">pluginDataSubscriber</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">metaDataSubscribers</span> <span class="o">=</span> <span class="n">metaDataSubscribers</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">authDataSubscribers</span> <span class="o">=</span> <span class="n">authDataSubscribers</span><span class="p">;</span>
        <span class="n">watcherData</span><span class="p">();</span>
        <span class="n">watchAppAuth</span><span class="p">();</span>
        <span class="n">watchMetaData</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherAll</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">pluginName</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">watcherPlugin</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">watcherSelector</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">watcherRule</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们在构造函数上打上断点，看看调用栈，发现是和HTTP同步差不多的，如下代码所示，可以看到使用Spring方式启动
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ZookeeperSyncDataConfiguration</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">SyncDataService</span> <span class="nf">syncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">ZkClient</span><span class="o">&gt;</span> <span class="n">zkClient</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">PluginDataSubscriber</span><span class="o">&gt;</span> <span class="n">pluginSubscriber</span><span class="p">,</span>
                                           <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">metaSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">authSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"you use zookeeper sync soul data......."</span><span class="p">);</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">ZookeeperSyncDataService</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(),</span> <span class="n">pluginSubscriber</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(),</span>
                <span class="n">metaSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">),</span> <span class="n">authSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">ZkClient</span> <span class="nf">zkClient</span><span class="p">(</span><span class="kd">final</span> <span class="n">ZookeeperConfig</span> <span class="n">zookeeperConfig</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">ZkClient</span><span class="p">(</span><span class="n">zookeeperConfig</span><span class="p">.</span><span class="na">getUrl</span><span class="p">(),</span> <span class="n">zookeeperConfig</span><span class="p">.</span><span class="na">getSessionTimeout</span><span class="p">(),</span> <span class="n">zookeeperConfig</span><span class="p">.</span><span class="na">getConnectionTimeout</span><span class="p">());</span>
    <span class="p">}</span>

<span class="p">}</span>
</code></pre>
     </div>
     <p>
      简单的调试和看了看，发现zk的同步方式和websocket一样还是比较简单的，结构也是非常的清晰，这里就不做过多的介绍了，就说明下其核心逻辑，代码如下：
     </p>
     <p>
      构造函数中启动多所有数据的监听，并进行五种树节点初始化
     </p>
     <p>
      需要注意理解的是对列表数据的监听（监听增删之类），监听列表中单个数据的变化，代码中的监听基本都是这种套路，注意理解即可
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ZookeeperSyncDataService</span> <span class="kd">implements</span> <span class="n">SyncDataService</span><span class="p">,</span> <span class="n">AutoCloseable</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="nf">ZookeeperSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ZkClient</span> <span class="n">zkClient</span><span class="p">,</span> <span class="kd">final</span> <span class="n">PluginDataSubscriber</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span>
                                    <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;</span> <span class="n">authDataSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">zkClient</span> <span class="o">=</span> <span class="n">zkClient</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">pluginDataSubscriber</span> <span class="o">=</span> <span class="n">pluginDataSubscriber</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">metaDataSubscribers</span> <span class="o">=</span> <span class="n">metaDataSubscribers</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">authDataSubscribers</span> <span class="o">=</span> <span class="n">authDataSubscribers</span><span class="p">;</span>
        <span class="c1">// 下面三个函数起到了初始化和启动监听的作用</span>
        <span class="n">watcherData</span><span class="p">();</span>
        <span class="n">watchAppAuth</span><span class="p">();</span>
        <span class="n">watchMetaData</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherData</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="n">String</span> <span class="n">pluginParent</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">PLUGIN_PARENT</span><span class="p">;</span>
        <span class="c1">// 获取插件列表，启动监听（在其中同时进行插件数据、选择器、规则的初始化）</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">pluginZKs</span> <span class="o">=</span> <span class="n">zkClientGetChildren</span><span class="p">(</span><span class="n">pluginParent</span><span class="p">);</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">String</span> <span class="n">pluginName</span> <span class="p">:</span> <span class="n">pluginZKs</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">watcherAll</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// 启动监听，数据更新（新增和修改），对更新的插件进行监听</span>
        <span class="n">zkClient</span><span class="p">.</span><span class="na">subscribeChildChanges</span><span class="p">(</span><span class="n">pluginParent</span><span class="p">,</span> <span class="p">(</span><span class="n">parentPath</span><span class="p">,</span> <span class="n">currentChildren</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">currentChildren</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">for</span> <span class="p">(</span><span class="n">String</span> <span class="n">pluginName</span> <span class="p">:</span> <span class="n">currentChildren</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">watcherAll</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherAll</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">pluginName</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">watcherPlugin</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">watcherSelector</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">watcherRule</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherPlugin</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">pluginName</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">pluginPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildPluginPath</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">zkClient</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">zkClient</span><span class="p">.</span><span class="na">createPersistent</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="c1">// 初始化插件数据</span>
        <span class="n">cachePluginData</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">readData</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">));</span>
        <span class="c1">// 监听插件数据</span>
        <span class="n">subscribePluginDataChanges</span><span class="p">(</span><span class="n">pluginPath</span><span class="p">,</span> <span class="n">pluginName</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 选择器结构大致是：插件 --&gt; 选择器列表 --&gt; 单个选择器</span>
    <span class="c1">// 所有最终需要对单个选择器进行初始化和监听</span>
    <span class="c1">// 并对上层的选择器列表进行监听，列表也会更新</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherSelector</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">pluginName</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">selectorParentPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildSelectorParentPath</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">childrenList</span> <span class="o">=</span> <span class="n">zkClientGetChildren</span><span class="p">(</span><span class="n">selectorParentPath</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">childrenList</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">childrenList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">children</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">String</span> <span class="n">realPath</span> <span class="o">=</span> <span class="n">buildRealPath</span><span class="p">(</span><span class="n">selectorParentPath</span><span class="p">,</span> <span class="n">children</span><span class="p">);</span>
                <span class="n">cacheSelectorData</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">readData</span><span class="p">(</span><span class="n">realPath</span><span class="p">));</span>
                <span class="n">subscribeSelectorDataChanges</span><span class="p">(</span><span class="n">realPath</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">}</span>
        <span class="n">subscribeChildChanges</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">SELECTOR</span><span class="p">,</span> <span class="n">selectorParentPath</span><span class="p">,</span> <span class="n">childrenList</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 原理同选择器</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watcherRule</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">pluginName</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">ruleParent</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">buildRuleParentPath</span><span class="p">(</span><span class="n">pluginName</span><span class="p">);</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">childrenList</span> <span class="o">=</span> <span class="n">zkClientGetChildren</span><span class="p">(</span><span class="n">ruleParent</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">childrenList</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">childrenList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">children</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">String</span> <span class="n">realPath</span> <span class="o">=</span> <span class="n">buildRealPath</span><span class="p">(</span><span class="n">ruleParent</span><span class="p">,</span> <span class="n">children</span><span class="p">);</span>
                <span class="n">cacheRuleData</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">readData</span><span class="p">(</span><span class="n">realPath</span><span class="p">));</span>
                <span class="n">subscribeRuleDataChanges</span><span class="p">(</span><span class="n">realPath</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">}</span>
        <span class="n">subscribeChildChanges</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">RULE</span><span class="p">,</span> <span class="n">ruleParent</span><span class="p">,</span> <span class="n">childrenList</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 原理类似选择器</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watchAppAuth</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="n">String</span> <span class="n">appAuthParent</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">APP_AUTH_PARENT</span><span class="p">;</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">childrenList</span> <span class="o">=</span> <span class="n">zkClientGetChildren</span><span class="p">(</span><span class="n">appAuthParent</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">childrenList</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">childrenList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">children</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">String</span> <span class="n">realPath</span> <span class="o">=</span> <span class="n">buildRealPath</span><span class="p">(</span><span class="n">appAuthParent</span><span class="p">,</span> <span class="n">children</span><span class="p">);</span>
                <span class="n">cacheAuthData</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">readData</span><span class="p">(</span><span class="n">realPath</span><span class="p">));</span>
                <span class="n">subscribeAppAuthDataChanges</span><span class="p">(</span><span class="n">realPath</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">}</span>
        <span class="n">subscribeChildChanges</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">APP_AUTH</span><span class="p">,</span> <span class="n">appAuthParent</span><span class="p">,</span> <span class="n">childrenList</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 原理类似选择器</span>
    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">watchMetaData</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">final</span> <span class="n">String</span> <span class="n">metaDataPath</span> <span class="o">=</span> <span class="n">ZkPathConstants</span><span class="p">.</span><span class="na">META_DATA</span><span class="p">;</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">childrenList</span> <span class="o">=</span> <span class="n">zkClientGetChildren</span><span class="p">(</span><span class="n">metaDataPath</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">childrenList</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">childrenList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">children</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">String</span> <span class="n">realPath</span> <span class="o">=</span> <span class="n">buildRealPath</span><span class="p">(</span><span class="n">metaDataPath</span><span class="p">,</span> <span class="n">children</span><span class="p">);</span>
                <span class="n">cacheMetaData</span><span class="p">(</span><span class="n">zkClient</span><span class="p">.</span><span class="na">readData</span><span class="p">(</span><span class="n">realPath</span><span class="p">));</span>
                <span class="n">subscribeMetaDataChanges</span><span class="p">(</span><span class="n">realPath</span><span class="p">);</span>
            <span class="p">});</span>
        <span class="p">}</span>
        <span class="n">subscribeChildChanges</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">APP_AUTH</span><span class="p">,</span> <span class="n">metaDataPath</span><span class="p">,</span> <span class="n">childrenList</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h2 id="_7">
      总结
     </h2>
     <p>
      总体来说，zookeeper同步方式还是比较容易理解的，代码结构也清晰
     </p>
     <p>
      可能没有用过zookeeper的会有点疑惑，这里就简单介绍下自己的理解：
     </p>
     <p>
      上面分析中提到的监听其核心是：发布-订阅
     </p>
     <p>
      zk客户端连接上zk服务端，当服务端的数据发送变化的时候，服务端会发送变化的数据给客户端，客户端收到变化的数据后进行操作
     </p>
     <p>
      这些变化的数据可以是目录（上面提到的列表数据），也可以是具体单个文件（列表中具体数据）
     </p>
     <p>
      理解了这个，大致就能理解zk同步的核心了
     </p>
     <p>
      总结下zookeeper的同步流程大致如下：
     </p>
     <ul>
      <li>
       1.读取初始数据进行初始化
      </li>
      <li>
       2.对读取的初始化数据进行监听
      </li>
      <li>
       3.读取变化的数据进行本地缓存更新，并对其进行监听
      </li>
     </ul>
     <h2 id="soul">
      Soul网关源码分析文章列表
     </h2>
     <h3 id="github">
      Github
     </h3>
     <ul>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB1-%E6%A6%82%E8%A7%88.md">
        Soul源码阅读（一） 概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB2-%E5%88%9D%E6%AD%A5%E8%BF%90%E8%A1%8C.md">
        Soul源码阅读（二）代码初步运行
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB3-%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%A6%82%E8%A7%88.md">
        Soul源码阅读（三）HTTP请求处理概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB4-dubbo%E8%AF%B7%E6%B1%82%E6%A6%82%E8%A7%88.md">
        Soul网关源码阅读（四）Dubbo请求概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB5-%E8%AF%B7%E6%B1%82%E7%B1%BB%E5%9E%8B%E6%8E%A2%E7%B4%A2.md">
        Soul网关源码阅读（五）请求类型探索
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB6-sofa%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%A6%82%E8%A7%88.md">
        Soul网关源码阅读（六）Sofa请求处理概览
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB7-%E9%99%90%E6%B5%81%E6%8F%92%E4%BB%B6%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（七）限流插件初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/0e6931519a84d5c603504b2c6a633698ac793b70/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB8-%E8%B7%AF%E7%94%B1%E5%8C%B9%E9%85%8D%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（八）路由匹配初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB9-%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（九）插件配置加载初探
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB10-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%AE%80%E5%8D%95%E6%8F%92%E4%BB%B6%E7%BC%96%E5%86%99.md">
        Soul网关源码阅读（十）自定义简单插件编写
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB11-%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E5%B0%8F%E7%BB%93.md">
        Soul网关源码阅读（十一）请求处理小结
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB12-%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5%E5%88%9D%E6%8E%A2.md">
        Soul网关源码阅读（十二）数据同步初探-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB13-websocket%E5%90%8C%E6%AD%A5%E6%95%B0%E6%8D%AE-Bootstrap%E7%AB%AF.md">
        Soul网关源码阅读（十三）Websocket同步数据-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB14-HTTP%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5-Bootstrap%E7%AB%AF.md">
        Soul网关源码阅读（十四）HTTP数据同步-Bootstrap端
       </a>
      </li>
      <li>
       <p>
        <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB15-Zookeeper%E6%95%B0%E6%8D%AE%E5%90%8C%E6%AD%A5-Bootstrap%E7%AB%AF.md">
         Soul网关源码阅读（十五）Zookeeper数据同步-Bootstrap端
        </a>
       </p>
      </li>
      <li>
       <p>
        <a href="https://github.com/lw1243925457/SE-Notes/blob/master/profession/program/%E5%BC%80%E6%BA%90/soul/soul%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%95%AA%E5%A4%96%E7%AF%871-HTTP%E7%A4%BA%E4%BE%8B%E5%8F%82%E6%95%B0%E8%AF%B7%E6%B1%82%E9%94%99%E8%AF%AF.md">
         Soul网关源码阅读番外篇（一） HTTP参数请求错误
        </a>
       </p>
      </li>
     </ul>
     <h3 id="_8">
      掘金
     </h3>
     <ul>
      <li>
       <a href="https://juejin.cn/post/6917864624423436296">
        Soul网关源码阅读（一） 概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917865804121767944">
        Soul网关源码阅读（二）代码初步运行
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917866538712334343">
        Soul网关源码阅读（三）请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917867369909977102">
        Soul网关源码阅读（四）Dubbo请求概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918575905962983438">
        Soul网关源码阅读（五）请求类型探索
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918736260467015693">
        Soul网关源码阅读（六）Sofa请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919348164944232455/">
        Soul网关源码阅读（七）限流插件初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919774553241550855/">
        Soul网关源码阅读（八）路由匹配初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920074307590684685/">
        Soul网关源码阅读（九）插件配置加载初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920142348617777166">
        Soul网关源码阅读（十）自定义简单插件编写
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596034171174925">
        Soul网关源码阅读（十一）请求处理小结
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596173925384206">
        Soul网关源码阅读（十二）数据同步初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596028505178125">
        Soul网关源码阅读（十三）Websocket同步数据-Bootstrap端
       </a>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6920597298674302983">
         Soul网关源码阅读（十四）HTTP数据同步-Bootstrap端
        </a>
       </p>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6918947689564471309">
         Soul网关源码阅读番外篇（一） HTTP参数请求错误
        </a>
       </p>
      </li>
     </ul>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
